const context = require('./context')
const request = require('./request')
const response = require('./response')

class Application {
    constructor(){
        this.middleware = []
        // 1.此时多个 application 共享同一个 context、response、request
        // 一个 application 中修改，其它实例都会被影响，所以借助 Object.create 拷贝一份
        this.context = Object.create(context)
        this.response = Object.create(response)
        this.request = Object.create(request)
    }
    listen(){
        const server = require('http').createServer(this.callback())
        server.listen(...arguments)
    }
    use(fn){
        this.middleware.push(fn)
    }
    compose(middleware){
        // 2.传入 context
        return function(context){
            const dispatch = index => {
                if(index >= middleware.length){
                    return Promise.resolve()
                }
                const fn = middleware[index]
                // 传入 context
                return Promise.resolve(
                    fn(context,()=>dispatch(index+1))
                )
            }
            return dispatch(0)
        }
    }
    callback(){
        const fnMiddleware = this.compose(this.middleware)
        const handleRequest = (req,res)=>{
            // 3.每个请求都会创建一个 context，不会污染
            const context = this.createContext(req,res)
            fnMiddleware(context).then(()=>{
               responsed(context) // 
            }).catch(err=>{
               res.end(err.message)
            })
        }
        return handleRequest
    }
    // 4.构造上下文
    createContext(req,res){
        // 不同请求由不同 context
        const context = Object.create(this.context)
        const request = (context.request = Object.create(this.request))
        const response = (context.response = Object.create(this.response))
        
        // ctx、request、response 挂载各种信息
        context.app = request.app = response.app = this
        context.req = request.req = response.req = req
        context.res = request.res = response.res = res
        request.ctx = response.ctx = context
        request.response = response
        response.request = request
        context.originalUrl = request.originalUrl = req.url
        context.state = {}
        return context
    }
}

const  {Stream}  = require('stream')
function responsed(ctx){
    const {body,res} = ctx
    if(typeof body === 'string') return res.end(body)
    if(Buffer.isBuffer(body)) return res.end(body)
    if (body instanceof Stream) return body.pipe(ctx.res)
    if (typeof body === 'number') return res.end(body + '')
    if (typeof body === 'object' && body !== null) {
        const jsonStr = JSON.stringify(body)
        return res.end(jsonStr)
    }
    res.statusCode = 204
    return res.end()
}

module.exports = Application